package net.sf.odinms.exttools.doclet.pages;

import java.io.IOException;
import java.util.*;
import com.sun.javadoc.ClassDoc;
import com.sun.javadoc.FieldDoc;
import com.sun.javadoc.PackageDoc;
import com.sun.tools.doclets.internal.toolkit.util.DocletConstants;
import com.sun.tools.doclets.internal.toolkit.util.Util;
import com.sun.tools.doclets.internal.toolkit.util.VisibleMemberMap;
import net.sf.odinms.exttools.doclet.AbstractPageWriter;
import net.sf.odinms.exttools.doclet.Doclet;

/**
 * Creates "constant-values.html", a list of items declared as final. The list
 * is alphabetized and grouped by package, then class. A list of packages is
 * included at top; it may use depth-limited package names to group like
 * packages together, depending on a user-specified flag.
 */
@SuppressWarnings("unchecked")
public final class ConstantValues extends AbstractPageWriter {
	
	/** A mapping of a package to member classes with constant fields. */
	private java.util.Map mapPackageToClasses;
	
	/** A mapping of a class to its member constant fields. */
	private java.util.Map mapClassToConstFields;
	
	// TODO Add doclet flag for short package name component length
	/** Flag to determine if shortened package names are used. */
	private boolean shortPackageNames =
			CONF.constantListMaxPackageComponents > 0;
	
	/** Used for storing a shortened name for the current package. */
	private String parsedPackageName;
	
	/**
	 * The abbreviated package headers that have already been printed.
	 */
	private Set printedPackageIndices = new HashSet();
	/**
	 * The abbreviated package anchors that have already been printed.
	 */
	private Set printedPackageAnchors = new HashSet();
	
	/**
	 * Generate a page listing all constant (declared "final") symbols.
	 */
	public static void generateConstantValues() {
		String filename = "constant-values" + CONF.ext;
		try {
			new ConstantValues(filename);
		}
		catch (IOException exc) {
			throw Doclet.exception(exc, filename);
		}
	}
	
	/**
	 * Initialize and create list of class members declared as final.
	 * 
	 * @param filename The desired name of the file (with extension).
	 * @throws IOException If {@link java.io.FileOutputStream} creation fails.
	 */
	private ConstantValues(String filename) throws IOException {
		super(filename);
		pageType = PageType.CONSTANTS;
		mapPackagesWithConstFields();
		
		windowTitle =
				(CONF.windowtitle.length() > 0)
						? CONF.windowtitle
						: CONF.propertyText("Constants_Summary");
		
		printXhtmlHeader();
		
		println(open("h1") + CONF.propertyText("Constants_By_Package")
				+ close("h1"));
		printPackageLinks();
		printConstantSummaries();
		
		printXhtmlFooter();
		this.close();
	}
	
	/**
	 * Create a mapping between a package and the classes in that package that
	 * contain constant fields. If no classes in the package have constant
	 * fields, it isn't added to the map.
	 */
	@SuppressWarnings("unchecked")
	private void mapPackagesWithConstFields() {
		PackageDoc[] packages = CONF.packages;
		PackageDoc pkg;
		ClassDoc[] classes;
		Set classesWithConstFields = new java.util.TreeSet();
		mapPackageToClasses = new TreeMap();
		mapClassToConstFields = new TreeMap();
		classesWithConstFields = new TreeSet();
		for (int p = 0; p < packages.length; p++) {
			pkg = packages[p];
			if (pkg.name().length() > 0) {
				classes = pkg.allClasses();
			} else {
				classes =
						CONF.classDocCatalog.allClasses(DocletConstants.DEFAULT_PACKAGE_NAME);
			}
			ClassDoc classDoc;
			Set constantFields;
			for (int c = 0; c < classes.length; c++) {
				classDoc = classes[c];
				if (classDoc.isIncluded()) {
					constantFields = visibleConstantFieldsInClass(classDoc);
					if (constantFields != null && constantFields.size() > 0) {
						classesWithConstFields.add(classDoc);
						mapClassToConstFields.put(classDoc, constantFields);
					}
				}
			}
			// Map non-empty class lists with corresponding package
			// Only create a new set if the previous one is non-empty
			if (classesWithConstFields.size() > 0) {
				mapPackageToClasses.put(pkg, classesWithConstFields);
				classesWithConstFields = new TreeSet();
			}
		}
		classesWithConstFields = null;
	}
	
	/**
	 * Return the list of visible constant fields for the given ClassDoc.
	 * 
	 * @param classDoc The class to scan for constant fields
	 * @return A {@link java.util.List} of visible constant fields.
	 */
	@SuppressWarnings("unchecked")
	private SortedSet visibleConstantFieldsInClass(ClassDoc classDoc) {
		VisibleMemberMap visibleMemberMapFields =
				new VisibleMemberMap(classDoc, VisibleMemberMap.FIELDS,
						CONF.nodeprecated);
		List fields = visibleMemberMapFields.getLeafClassMembers(CONF);
		if (fields == null)
			return null;
		SortedSet constantFields = new TreeSet();
		FieldDoc field;
		for (Iterator iter = fields.iterator(); iter.hasNext();) {
			field = (FieldDoc) iter.next();
			if (field.constantValueExpression() != null) {
				constantFields.add(field);
			}
		}
		return constantFields;
	}
	
	/**
	 * Print a sorted list of packages containing constant field values. If
	 * {@link #shortPackageNames} is true, package names are truncated and
	 * packages with the common prefix are lumped together to reduce the number
	 * of packages in the list at top.
	 */
	// TODO Would it be desirable to create an expanding list of packages?
	@SuppressWarnings("unchecked")
	private void printPackageLinks() {
		println(open("ul"));
		String unnamedPackageLink =
				linkToLabelHref(DocletConstants.DEFAULT_PACKAGE_NAME, "#"
						+ DocletConstants.UNNAMED_PACKAGE_ANCHOR);
		// writeLinkToPackageContent() for each relevant package
		
		PackageDoc pkg;
		String pkgName;
		Iterator packages = mapPackageToClasses.keySet().iterator();
		while (packages.hasNext()) {
			pkg = (PackageDoc) packages.next();
			pkgName = pkg.name();
			if (shortPackageNames) {
				parsedPackageName = parsePackageName(pkgName);
				if (!printedPackageIndices.contains(parsedPackageName)) {
					// Adds link to summary
					if (pkgName.length() == 0) {
						println(listItem(unnamedPackageLink));
					} else {
						println(listItem(linkToLabelHref(parsedPackageName
								+ ".*", "#" + parsedPackageName)));
						printedPackageIndices.add(parsedPackageName);
					}
				}
			} else
				println(listItem(linkToLabelHref(pkgName, "#" + pkgName)));
		}
		println(close("ul") + "\n");
	}
	
	/**
	 * Print summaries of all constant field values by package, then class.
	 */
	@SuppressWarnings("unchecked")
	private void printConstantSummaries() {
		Iterator packages = mapPackageToClasses.keySet().iterator(), classes, fields;
		PackageDoc pkg;
		ClassDoc cd;
		FieldDoc field;
		String pkgName, classHref;
		while (packages.hasNext()) {
			pkg = (PackageDoc) packages.next();
			pkgName = pkg.name();
			// When using abbreviated packages, add anchor for first in group
			parsedPackageName = parsePackageName(pkgName);
			if (shortPackageNames
					&& !printedPackageAnchors.contains(parsedPackageName)) {
				println(new TagBuilder("a").add("name", parsedPackageName).getEmpty());
				printedPackageAnchors.add(parsedPackageName);
			}
			println(openDivWithID(pkgName));
			println(open("h2") + linkToPackage(pkg) + close("h2"));
			classes = ((Set) mapPackageToClasses.get(pkg)).iterator();
			// Print a table of constants for each class in package
			while (classes.hasNext()) {
				cd = (ClassDoc) classes.next();
				classHref = hrefToDoc(cd);
				println(open("h3")
						+ linkToLabelHrefTargetTitle(cd.name(), classHref,
								null, classFlavor(cd, false) + " in " + pkg)
						+ close("h3"));
				fields = ((Set) mapClassToConstFields.get(cd)).iterator();
				println(open("table"));
				// Print a row for each constant-valued field in a class
				while (fields.hasNext()) {
					field = (FieldDoc) fields.next();
					println(open("tr"));
					printTDWithClass(field.modifiers() + " "
							+ linkToType(field), "prefix");
					// Print link to field in class, plus incoming named anchor
					printTDWithClass(linkToLabelHrefName(field.name(),
							classHref + "#" + field.name(),
							field.qualifiedName()), "name");
					printTDWithClass(
							Util.escapeHtmlChars(field.constantValueExpression()),
							"value");
					println(close("tr"));
				}
				println(close("table"));
			}
			println(close("div") + getComment(pkgName) + "\n");
		}
	}
	
	/**
	 * Parse a package name so as to only display package name up to 2 levels.
	 * This reduces the number of package names in the list.
	 * 
	 * @param pkgName The name of the package to parse.
	 * @return A shortened package name (with * at the end) so classes that
	 *         share ancestry are lumped together.
	 */
	// TODO Fix parsing for packages with fewer components than the limit.
	private String parsePackageName(String pkgName) {
		int index = -1;
		for (int j = 0; j < CONF.constantListMaxPackageComponents; j++) {
			index = pkgName.indexOf(".", index + 1);
		}
		if (index != -1) {
			pkgName = pkgName.substring(0, index);
		}
		return pkgName;
	}
	
}
